{
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.9-final"
  },
  "orig_nbformat": 2,
  "kernelspec": {
   "name": "pycharm-f7427e7c",
   "display_name": "PyCharm (pythonProject)",
   "language": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2,
 "cells": [
  {
   "source": [
    "<center><h1>第四章 分组</h1></center>"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd"
   ]
  },
  {
   "source": [
    "## 一、分组模式及其对象\n",
    "### 1. 分组的一般模式\n",
    "分组操作在日常生活中使用极其广泛，例如：\n",
    "\n",
    "* 依据$\\color{#FF0000}{性别}$分组，统计全国人口$\\color{#00FF00}{寿命}$的$\\color{#0000FF}{平均值}$\n",
    "* 依据$\\color{#FF0000}{季节}$分组，对每一个季节的$\\color{#00FF00}{温度}$进行$\\color{#0000FF}{组内标准化}$\n",
    "* 依据$\\color{#FF0000}{班级}$筛选出组内$\\color{#00FF00}{数学分数}$的$\\color{#0000FF}{平均值超过80分的班级}$"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "从上述的几个例子中不难看出，想要实现分组操作，必须明确三个要素：$\\color{#FF0000}{分组依据}$、$\\color{#00FF00}{数据来源}$、$\\color{#0000FF}{操作及其返回结果}$。同时从充分性的角度来说，如果明确了这三方面，就能确定一个分组操作，从而分组代码的一般模式即：\n",
    "```\n",
    "df.groupby(分组依据)[数据来源].使用操作\n",
    "```\n",
    "例如第一个例子中的代码就应该如下：\n",
    "```\n",
    "df.groupby('Gender')['Longevity'].mean()\n",
    "```"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "现在返回到学生体测的数据集上，如果想要按照性别统计身高中位数，就可以如下写出："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Gender\n",
       "Female    159.6\n",
       "Male      173.4\n",
       "Name: Height, dtype: float64"
      ]
     },
     "metadata": {},
     "execution_count": 2
    }
   ],
   "source": [
    "df = pd.read_csv('../data/learn_pandas.csv')\n",
    "df.groupby('Gender')['Height'].median()"
   ]
  },
  {
   "source": [
    "### 2. 分组依据的本质\n",
    "前面提到的若干例子都是以单一维度进行分组的，比如根据性别，如果现在需要根据多个维度进行分组，该如何做？事实上，只需在`groupby`中传入相应列名构成的列表即可。例如，现希望根据学校和性别进行分组，统计身高的均值就可以如下写出："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "School                         Gender\n",
       "Fudan University               Female    158.776923\n",
       "                               Male      174.212500\n",
       "Peking University              Female    158.666667\n",
       "                               Male      172.030000\n",
       "Shanghai Jiao Tong University  Female    159.122500\n",
       "                               Male      176.760000\n",
       "Tsinghua University            Female    159.753333\n",
       "                               Male      171.638889\n",
       "Name: Height, dtype: float64"
      ]
     },
     "metadata": {},
     "execution_count": 3
    }
   ],
   "source": [
    "df.groupby(['School', 'Gender'])['Height'].mean()"
   ]
  },
  {
   "source": [
    "目前为止，`groupby`的分组依据都是直接可以从列中按照名字获取的，那如果希望通过一定的复杂逻辑来分组，例如根据学生体重是否超过总体均值来分组，同样还是计算身高的均值。\n",
    "\n",
    "首先应该先写出分组条件："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "condition = df.Weight > df.Weight.mean()"
   ]
  },
  {
   "source": [
    "然后将其传入`groupby`中："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "df.groupby(condition)['Height'].mean()"
   ],
   "cell_type": "code",
   "metadata": {},
   "execution_count": 5,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Weight\n",
       "False    159.034646\n",
       "True     172.705357\n",
       "Name: Height, dtype: float64"
      ]
     },
     "metadata": {},
     "execution_count": 5
    }
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "请根据上下四分位数分割，将体重分为high、normal、low三组，统计身高的均值。\n",
    "#### 【END】\n",
    "从索引可以看出，其实最后产生的结果就是按照条件列表中元素的值（此处是`True`和`False`）来分组，下面用随机传入字母序列来验证这一想法："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "a    163.094828\n",
       "b    163.874603\n",
       "c    162.666129\n",
       "Name: Height, dtype: float64"
      ]
     },
     "metadata": {},
     "execution_count": 6
    }
   ],
   "source": [
    "item = np.random.choice(list('abc'), df.shape[0])\n",
    "df.groupby(item)['Height'].mean()"
   ]
  },
  {
   "source": [
    "此处的索引就是原先item中的元素，如果传入多个序列进入`groupby`，那么最后分组的依据就是这两个序列对应行的唯一组合："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Weight   \n",
       "False   a    159.334146\n",
       "        b    159.257143\n",
       "        c    158.543182\n",
       "True    a    172.164706\n",
       "        b    173.109524\n",
       "        c    172.744444\n",
       "Name: Height, dtype: float64"
      ]
     },
     "metadata": {},
     "execution_count": 7
    }
   ],
   "source": [
    "df.groupby([condition, item])['Height'].mean()"
   ]
  },
  {
   "source": [
    "由此可以看出，之前传入列名只是一种简便的记号，事实上等价于传入的是一个或多个列，最后分组的依据来自于数据来源组合的unique值，通过`drop_duplicates`就能知道具体的组类别："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "                           School  Gender\n",
       "0   Shanghai Jiao Tong University  Female\n",
       "1               Peking University    Male\n",
       "2   Shanghai Jiao Tong University    Male\n",
       "3                Fudan University  Female\n",
       "4                Fudan University    Male\n",
       "5             Tsinghua University  Female\n",
       "9               Peking University  Female\n",
       "16            Tsinghua University    Male"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>School</th>\n      <th>Gender</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>Shanghai Jiao Tong University</td>\n      <td>Female</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>Peking University</td>\n      <td>Male</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>Shanghai Jiao Tong University</td>\n      <td>Male</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>Fudan University</td>\n      <td>Female</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>Fudan University</td>\n      <td>Male</td>\n    </tr>\n    <tr>\n      <th>5</th>\n      <td>Tsinghua University</td>\n      <td>Female</td>\n    </tr>\n    <tr>\n      <th>9</th>\n      <td>Peking University</td>\n      <td>Female</td>\n    </tr>\n    <tr>\n      <th>16</th>\n      <td>Tsinghua University</td>\n      <td>Male</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 8
    }
   ],
   "source": [
    "df[['School', 'Gender']].drop_duplicates()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "School                         Gender\n",
       "Fudan University               Female    158.776923\n",
       "                               Male      174.212500\n",
       "Peking University              Female    158.666667\n",
       "                               Male      172.030000\n",
       "Shanghai Jiao Tong University  Female    159.122500\n",
       "                               Male      176.760000\n",
       "Tsinghua University            Female    159.753333\n",
       "                               Male      171.638889\n",
       "Name: Height, dtype: float64"
      ]
     },
     "metadata": {},
     "execution_count": 9
    }
   ],
   "source": [
    "df.groupby([df['School'], df['Gender']])['Height'].mean()"
   ]
  },
  {
   "source": [
    "### 3. Groupby对象\n",
    "能够注意到，最终具体做分组操作时，所调用的方法都来自于`pandas`中的`groupby`对象，这个对象上定义了许多方法，也具有一些方便的属性。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001C1E7AB1408>"
      ]
     },
     "metadata": {},
     "execution_count": 10
    }
   ],
   "source": [
    "gb = df.groupby(['School', 'Grade'])\n",
    "gb"
   ]
  },
  {
   "source": [
    "通过`ngroups`属性，可以得到分组个数："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "16"
      ]
     },
     "metadata": {},
     "execution_count": 11
    }
   ],
   "source": [
    "gb.ngroups"
   ]
  },
  {
   "source": [
    "通过`groups`属性，可以返回从$\\color{#FF0000}{组名}$映射到$\\color{#FF0000}{组索引列表}$的字典："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "dict_keys([('Fudan University', 'Freshman'), ('Fudan University', 'Junior'), ('Fudan University', 'Senior'), ('Fudan University', 'Sophomore'), ('Peking University', 'Freshman'), ('Peking University', 'Junior'), ('Peking University', 'Senior'), ('Peking University', 'Sophomore'), ('Shanghai Jiao Tong University', 'Freshman'), ('Shanghai Jiao Tong University', 'Junior'), ('Shanghai Jiao Tong University', 'Senior'), ('Shanghai Jiao Tong University', 'Sophomore'), ('Tsinghua University', 'Freshman'), ('Tsinghua University', 'Junior'), ('Tsinghua University', 'Senior'), ('Tsinghua University', 'Sophomore')])"
      ]
     },
     "metadata": {},
     "execution_count": 12
    }
   ],
   "source": [
    "res = gb.groups\n",
    "res.keys() # 字典的值由于是索引，元素个数过多，此处只展示字典的键"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "上一小节介绍了可以通过`drop_duplicates`得到具体的组类别，现请用`groups`属性完成类似的功能。\n",
    "#### 【END】\n",
    "当`size`作为`DataFrame`的属性时，返回的是表长乘以表宽的大小，但在`groupby`对象上表示统计每个组的元素个数："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "School                         Grade    \n",
       "Fudan University               Freshman      9\n",
       "                               Junior       12\n",
       "                               Senior       11\n",
       "                               Sophomore     8\n",
       "Peking University              Freshman     13\n",
       "                               Junior        8\n",
       "                               Senior        8\n",
       "                               Sophomore     5\n",
       "Shanghai Jiao Tong University  Freshman     13\n",
       "                               Junior       17\n",
       "                               Senior       22\n",
       "                               Sophomore     5\n",
       "Tsinghua University            Freshman     17\n",
       "                               Junior       22\n",
       "                               Senior       14\n",
       "                               Sophomore    16\n",
       "dtype: int64"
      ]
     },
     "metadata": {},
     "execution_count": 13
    }
   ],
   "source": [
    "gb.size()"
   ]
  },
  {
   "source": [
    "通过`get_group`方法可以直接获取所在组对应的行，此时必须知道组的具体名字："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "               School     Grade             Name  Gender  Height  Weight  \\\n",
       "15   Fudan University  Freshman  Changqiang Yang  Female   156.0    49.0   \n",
       "28   Fudan University  Freshman     Gaoqiang Qin  Female   170.2    63.0   \n",
       "63   Fudan University  Freshman     Gaofeng Zhao  Female   152.2    43.0   \n",
       "70   Fudan University  Freshman     Yanquan Wang  Female   163.5    55.0   \n",
       "73   Fudan University  Freshman        Feng Wang    Male   176.3    74.0   \n",
       "105  Fudan University  Freshman        Qiang Shi  Female   164.5    52.0   \n",
       "108  Fudan University  Freshman      Yanqiang Xu  Female   152.4    38.0   \n",
       "157  Fudan University  Freshman        Xiaoli Lv  Female   152.5    45.0   \n",
       "186  Fudan University  Freshman     Yanjuan Zhao  Female     NaN    53.0   \n",
       "\n",
       "    Transfer  Test_Number   Test_Date Time_Record  \n",
       "15         N            3    2020/1/1     0:05:25  \n",
       "28         N            2    2020/1/7     0:05:24  \n",
       "63         N            2  2019/10/31     0:04:00  \n",
       "70         N            1  2019/11/19     0:04:07  \n",
       "73         N            1   2019/9/26     0:03:31  \n",
       "105        N            1  2019/12/11     0:04:23  \n",
       "108        N            1   2019/12/8     0:05:03  \n",
       "157        N            2   2019/9/11     0:04:17  \n",
       "186        N            2   2019/10/9     0:04:21  "
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>School</th>\n      <th>Grade</th>\n      <th>Name</th>\n      <th>Gender</th>\n      <th>Height</th>\n      <th>Weight</th>\n      <th>Transfer</th>\n      <th>Test_Number</th>\n      <th>Test_Date</th>\n      <th>Time_Record</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>15</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Changqiang Yang</td>\n      <td>Female</td>\n      <td>156.0</td>\n      <td>49.0</td>\n      <td>N</td>\n      <td>3</td>\n      <td>2020/1/1</td>\n      <td>0:05:25</td>\n    </tr>\n    <tr>\n      <th>28</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Gaoqiang Qin</td>\n      <td>Female</td>\n      <td>170.2</td>\n      <td>63.0</td>\n      <td>N</td>\n      <td>2</td>\n      <td>2020/1/7</td>\n      <td>0:05:24</td>\n    </tr>\n    <tr>\n      <th>63</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Gaofeng Zhao</td>\n      <td>Female</td>\n      <td>152.2</td>\n      <td>43.0</td>\n      <td>N</td>\n      <td>2</td>\n      <td>2019/10/31</td>\n      <td>0:04:00</td>\n    </tr>\n    <tr>\n      <th>70</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Yanquan Wang</td>\n      <td>Female</td>\n      <td>163.5</td>\n      <td>55.0</td>\n      <td>N</td>\n      <td>1</td>\n      <td>2019/11/19</td>\n      <td>0:04:07</td>\n    </tr>\n    <tr>\n      <th>73</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Feng Wang</td>\n      <td>Male</td>\n      <td>176.3</td>\n      <td>74.0</td>\n      <td>N</td>\n      <td>1</td>\n      <td>2019/9/26</td>\n      <td>0:03:31</td>\n    </tr>\n    <tr>\n      <th>105</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Qiang Shi</td>\n      <td>Female</td>\n      <td>164.5</td>\n      <td>52.0</td>\n      <td>N</td>\n      <td>1</td>\n      <td>2019/12/11</td>\n      <td>0:04:23</td>\n    </tr>\n    <tr>\n      <th>108</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Yanqiang Xu</td>\n      <td>Female</td>\n      <td>152.4</td>\n      <td>38.0</td>\n      <td>N</td>\n      <td>1</td>\n      <td>2019/12/8</td>\n      <td>0:05:03</td>\n    </tr>\n    <tr>\n      <th>157</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Xiaoli Lv</td>\n      <td>Female</td>\n      <td>152.5</td>\n      <td>45.0</td>\n      <td>N</td>\n      <td>2</td>\n      <td>2019/9/11</td>\n      <td>0:04:17</td>\n    </tr>\n    <tr>\n      <th>186</th>\n      <td>Fudan University</td>\n      <td>Freshman</td>\n      <td>Yanjuan Zhao</td>\n      <td>Female</td>\n      <td>NaN</td>\n      <td>53.0</td>\n      <td>N</td>\n      <td>2</td>\n      <td>2019/10/9</td>\n      <td>0:04:21</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 14
    }
   ],
   "source": [
    "gb.get_group(('Fudan University', 'Freshman'))"
   ]
  },
  {
   "source": [
    "这里列出了2个属性和2个方法，而先前的`mean`、`median`都是`groupby`对象上的方法，这些函数和许多其他函数的操作具有高度相似性，将在之后的小节进行专门介绍。\n",
    "### 4. 分组的三大操作\n",
    "熟悉了一些分组的基本知识后，重新回到开头举的三个例子，可能会发现一些端倪，即这三种类型分组返回的数据型态并不一样：\n",
    "\n",
    "* 第一个例子中，每一个组返回一个标量值，可以是平均值、中位数、组容量`size`等\n",
    "* 第二个例子中，做了原序列的标准化处理，也就是说每组返回的是一个`Series`类型\n",
    "* 第三个例子中，既不是标量也不是序列，返回的整个组所在行的本身，即返回了`DataFrame`类型\n",
    "\n",
    "由此，引申出分组的三大操作：聚合、变换和过滤，分别对应了三个例子的操作，下面就要分别介绍相应的`agg`、`transform`和`filter`函数及其操作。\n",
    "## 二、聚合函数\n",
    "### 1. 内置聚合函数\n",
    "在介绍agg之前，首先要了解一些直接定义在groupby对象的聚合函数，因为它的速度基本都会经过内部的优化，使用功能时应当优先考虑。根据返回标量值的原则，包括如下函数：`max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod`。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Gender\n",
       "Female    143\n",
       "Male      199\n",
       "Name: Height, dtype: int64"
      ]
     },
     "metadata": {},
     "execution_count": 15
    }
   ],
   "source": [
    "gb = df.groupby('Gender')['Height']\n",
    "gb.idxmin()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Gender\n",
       "Female    166.8\n",
       "Male      185.9\n",
       "Name: Height, dtype: float64"
      ]
     },
     "metadata": {},
     "execution_count": 16
    }
   ],
   "source": [
    "gb.quantile(0.95)"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "请查阅文档，明确`all/any/mad/skew/sem/prod`函数的含义。\n",
    "#### 【END】\n",
    "这些聚合函数当传入的数据来源包含多个列时，将按照列进行迭代计算："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "        Height  Weight\n",
       "Gender                \n",
       "Female   170.2    63.0\n",
       "Male     193.9    89.0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>Height</th>\n      <th>Weight</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>170.2</td>\n      <td>63.0</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>193.9</td>\n      <td>89.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 17
    }
   ],
   "source": [
    "gb = df.groupby('Gender')[['Height', 'Weight']]\n",
    "gb.max()"
   ]
  },
  {
   "source": [
    "### 2. agg方法\n",
    "虽然在`groupby`对象上定义了许多方便的函数，但仍然有以下不便之处：\n",
    "\n",
    "* 无法同时使用多个函数\n",
    "* 无法对特定的列使用特定的聚合函数\n",
    "* 无法使用自定义的聚合函数\n",
    "* 无法直接对结果的列名在聚合前进行自定义命名\n",
    "\n",
    "下面说明如何通过`agg`函数解决这四类问题：\n",
    "\n",
    "【a】使用多个函数\n",
    "\n",
    "当使用多个聚合函数时，需要用列表的形式把内置聚合函数对应的字符串传入，先前提到的所有字符串都是合法的。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "         Height                   Weight                 \n",
       "            sum idxmax      skew     sum idxmax      skew\n",
       "Gender                                                   \n",
       "Female  21014.0     28 -0.219253  6469.0     28 -0.268482\n",
       "Male     8854.9    193  0.437535  3929.0      2 -0.332393"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead tr th {\n        text-align: left;\n    }\n\n    .dataframe thead tr:last-of-type th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr>\n      <th></th>\n      <th colspan=\"3\" halign=\"left\">Height</th>\n      <th colspan=\"3\" halign=\"left\">Weight</th>\n    </tr>\n    <tr>\n      <th></th>\n      <th>sum</th>\n      <th>idxmax</th>\n      <th>skew</th>\n      <th>sum</th>\n      <th>idxmax</th>\n      <th>skew</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n      <th></th>\n      <th></th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>21014.0</td>\n      <td>28</td>\n      <td>-0.219253</td>\n      <td>6469.0</td>\n      <td>28</td>\n      <td>-0.268482</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>8854.9</td>\n      <td>193</td>\n      <td>0.437535</td>\n      <td>3929.0</td>\n      <td>2</td>\n      <td>-0.332393</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 18
    }
   ],
   "source": [
    "gb.agg(['sum', 'idxmax', 'skew'])"
   ]
  },
  {
   "source": [
    "从结果看，此时的列索引为多级索引，第一层为数据源，第二层为使用的聚合方法，分别逐一对列使用聚合，因此结果为6列。\n",
    "\n",
    "【b】对特定的列使用特定的聚合函数\n",
    "\n",
    "对于方法和列的特殊对应，可以通过构造字典传入`agg`中实现，其中字典以列名为键，以聚合字符串或字符串列表为值。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "           Height        Weight\n",
       "             mean    max  count\n",
       "Gender                         \n",
       "Female  159.19697  170.2    135\n",
       "Male    173.62549  193.9     54"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead tr th {\n        text-align: left;\n    }\n\n    .dataframe thead tr:last-of-type th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr>\n      <th></th>\n      <th colspan=\"2\" halign=\"left\">Height</th>\n      <th>Weight</th>\n    </tr>\n    <tr>\n      <th></th>\n      <th>mean</th>\n      <th>max</th>\n      <th>count</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>159.19697</td>\n      <td>170.2</td>\n      <td>135</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>173.62549</td>\n      <td>193.9</td>\n      <td>54</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 19
    }
   ],
   "source": [
    "gb.agg({'Height':['mean','max'], 'Weight':'count'})"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "请使用【b】中的传入字典的方法完成【a】中等价的聚合任务。\n",
    "#### 【END】\n",
    "【c】使用自定义函数\n",
    "\n",
    "在`agg`中可以使用具体的自定义函数，$\\color{#FF0000}{需要注意传入函数的参数是之前数据源中的列，逐列进行计算}$。下面分组计算身高和体重的极差："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "          Height     Weight\n",
       "Gender                     \n",
       "Female  13.79697  13.918519\n",
       "Male    17.92549  21.759259"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>Height</th>\n      <th>Weight</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>13.79697</td>\n      <td>13.918519</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>17.92549</td>\n      <td>21.759259</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 20
    }
   ],
   "source": [
    "gb.agg(lambda x: x.mean()-x.min())"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "在`groupby`对象中可以使用`describe`方法进行统计信息汇总，请同时使用多个聚合函数，完成与该方法相同的功能。\n",
    "#### 【END】\n",
    "由于传入的是序列，因此序列上的方法和属性都是可以在函数中使用的，只需保证返回值是标量即可。下面的例子是指，如果组的指标均值，超过该指标的总体均值，返回High，否则返回Low。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "       Height Weight\n",
       "Gender              \n",
       "Female    Low    Low\n",
       "Male     High   High"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>Height</th>\n      <th>Weight</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>Low</td>\n      <td>Low</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>High</td>\n      <td>High</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 21
    }
   ],
   "source": [
    "def my_func(s):\n",
    "    res = 'High'\n",
    "    if s.mean() <= df[s.name].mean():\n",
    "        res = 'Low'\n",
    "    return res\n",
    "gb.agg(my_func)"
   ]
  },
  {
   "source": [
    "【d】聚合结果重命名\n",
    "\n",
    "如果想要对聚合结果的列名进行重命名，只需要将上述函数的位置改写成元组，元组的第一个元素为新的名字，第二个位置为原来的函数，包括聚合字符串和自定义函数，现举若干例子说明："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "       Height          Weight        \n",
       "        range   my_sum  range  my_sum\n",
       "Gender                               \n",
       "Female   24.8  21014.0   29.0  6469.0\n",
       "Male     38.2   8854.9   38.0  3929.0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead tr th {\n        text-align: left;\n    }\n\n    .dataframe thead tr:last-of-type th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr>\n      <th></th>\n      <th colspan=\"2\" halign=\"left\">Height</th>\n      <th colspan=\"2\" halign=\"left\">Weight</th>\n    </tr>\n    <tr>\n      <th></th>\n      <th>range</th>\n      <th>my_sum</th>\n      <th>range</th>\n      <th>my_sum</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>24.8</td>\n      <td>21014.0</td>\n      <td>29.0</td>\n      <td>6469.0</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>38.2</td>\n      <td>8854.9</td>\n      <td>38.0</td>\n      <td>3929.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 22
    }
   ],
   "source": [
    "gb.agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])"
   ]
  },
  {
   "source": [
    "gb.agg({'Height': [('my_func', my_func), 'sum'], 'Weight': lambda x:x.max()})"
   ],
   "cell_type": "code",
   "metadata": {},
   "execution_count": 23,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "        Height            Weight\n",
       "       my_func      sum <lambda>\n",
       "Gender                          \n",
       "Female     Low  21014.0     63.0\n",
       "Male      High   8854.9     89.0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead tr th {\n        text-align: left;\n    }\n\n    .dataframe thead tr:last-of-type th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr>\n      <th></th>\n      <th colspan=\"2\" halign=\"left\">Height</th>\n      <th>Weight</th>\n    </tr>\n    <tr>\n      <th></th>\n      <th>my_func</th>\n      <th>sum</th>\n      <th>&lt;lambda&gt;</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>Low</td>\n      <td>21014.0</td>\n      <td>63.0</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>High</td>\n      <td>8854.9</td>\n      <td>89.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 23
    }
   ]
  },
  {
   "source": [
    "另外需要注意，使用对一个或者多个列使用单个聚合的时候，重命名需要加方括号，否则就不知道是新的名字还是手误输错的内置函数字符串："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "         Height  Weight\n",
       "         my_sum  my_sum\n",
       "Gender                 \n",
       "Female  21014.0  6469.0\n",
       "Male     8854.9  3929.0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead tr th {\n        text-align: left;\n    }\n\n    .dataframe thead tr:last-of-type th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr>\n      <th></th>\n      <th>Height</th>\n      <th>Weight</th>\n    </tr>\n    <tr>\n      <th></th>\n      <th>my_sum</th>\n      <th>my_sum</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>21014.0</td>\n      <td>6469.0</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>8854.9</td>\n      <td>3929.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 24
    }
   ],
   "source": [
    "gb.agg([('my_sum', 'sum')])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "        Height          Weight\n",
       "       my_func      sum  range\n",
       "Gender                        \n",
       "Female     Low  21014.0   63.0\n",
       "Male      High   8854.9   89.0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead tr th {\n        text-align: left;\n    }\n\n    .dataframe thead tr:last-of-type th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr>\n      <th></th>\n      <th colspan=\"2\" halign=\"left\">Height</th>\n      <th>Weight</th>\n    </tr>\n    <tr>\n      <th></th>\n      <th>my_func</th>\n      <th>sum</th>\n      <th>range</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th></th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>Female</th>\n      <td>Low</td>\n      <td>21014.0</td>\n      <td>63.0</td>\n    </tr>\n    <tr>\n      <th>Male</th>\n      <td>High</td>\n      <td>8854.9</td>\n      <td>89.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 25
    }
   ],
   "source": [
    "gb.agg({'Height': [('my_func', my_func), 'sum'], 'Weight': [('range', lambda x:x.max())]})"
   ]
  },
  {
   "source": [
    "## 三、变换和过滤\n",
    "### 1. 变换函数与transform方法\n",
    "变换函数的返回值为同长度的序列，最常用的内置变换函数是累计函数：`cumcount/cumsum/cumprod/cummax/cummin`，它们的使用方式和聚合函数类似，只不过完成的是组内累计操作。此外在`groupby`对象上还定义了填充类和滑窗类的变换函数，这些函数的一般形式将会分别在第七章和第十章中讨论，此处略过。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "   Height  Weight\n",
       "0   158.9    46.0\n",
       "1   166.5    70.0\n",
       "2   188.9    89.0\n",
       "3     NaN    46.0\n",
       "4   188.9    89.0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>Height</th>\n      <th>Weight</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>158.9</td>\n      <td>46.0</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>166.5</td>\n      <td>70.0</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>188.9</td>\n      <td>89.0</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>NaN</td>\n      <td>46.0</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>188.9</td>\n      <td>89.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 26
    }
   ],
   "source": [
    "gb.cummax().head()"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "在`groupby`对象中，`rank`方法也是一个实用的变换函数，请查阅它的功能并给出一个使用的例子。\n",
    "#### 【END】\n",
    "当用自定义变换时需要使用`transform`方法，被调用的自定义函数，$\\color{#FF0000}{其传入值为数据源的序列}$，与`agg`的传入类型是一致的，其最后的返回结果是行列索引与数据源一致的`DataFrame`。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "现对身高和体重进行分组标准化，即减去组均值后除以组的标准差："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "     Height    Weight\n",
       "0 -0.058760 -0.354888\n",
       "1 -1.010925 -0.355000\n",
       "2  2.167063  2.089498\n",
       "3       NaN -1.279789\n",
       "4  0.053133  0.159631"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>Height</th>\n      <th>Weight</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>-0.058760</td>\n      <td>-0.354888</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>-1.010925</td>\n      <td>-0.355000</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>2.167063</td>\n      <td>2.089498</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>NaN</td>\n      <td>-1.279789</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>0.053133</td>\n      <td>0.159631</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 27
    }
   ],
   "source": [
    "gb.transform(lambda x: (x-x.mean())/x.std()).head()"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "对于`transform`方法无法像`agg`一样，通过传入字典来对指定列使用特定的变换，如果需要在一次`transform`的调用中实现这种功能，请给出解决方案。\n",
    "#### 【END】\n",
    "前面提到了`transform`只能返回同长度的序列，但事实上还可以返回一个标量，这会使得结果被广播到其所在的整个组，这种$\\color{#FF0000}{标量广播}$的技巧在特征工程中是非常常见的。例如，构造两列新特征来分别表示样本所在性别组的身高均值和体重均值："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "      Height     Weight\n",
       "0  159.19697  47.918519\n",
       "1  173.62549  72.759259\n",
       "2  173.62549  72.759259\n",
       "3  159.19697  47.918519\n",
       "4  173.62549  72.759259"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>Height</th>\n      <th>Weight</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>159.19697</td>\n      <td>47.918519</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>173.62549</td>\n      <td>72.759259</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>173.62549</td>\n      <td>72.759259</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>159.19697</td>\n      <td>47.918519</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>173.62549</td>\n      <td>72.759259</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 28
    }
   ],
   "source": [
    "gb.transform('mean').head() # 传入返回标量的函数也是可以的"
   ]
  },
  {
   "source": [
    "### 2. 组索引与过滤\n",
    "\n",
    "在上一章中介绍了索引的用法，那么索引和过滤有什么区别呢？\n",
    "\n",
    "过滤在分组中是对于组的过滤，而索引是对于行的过滤，在第二章中的返回值，无论是布尔列表还是元素列表或者位置列表，本质上都是对于行的筛选，即如果符合筛选条件的则选入结果表，否则不选入。\n",
    "\n",
    "组过滤作为行过滤的推广，指的是如果对一个组的全体所在行进行统计的结果返回`True`则会被保留，`False`则该组会被过滤，最后把所有未被过滤的组其对应的所在行拼接起来作为`DataFrame`返回。\n",
    "\n",
    "在`groupby`对象中，定义了`filter`方法进行组的筛选，其中自定义函数的输入参数为数据源构成的`DataFrame`本身，在之前例子中定义的`groupby`对象中，传入的就是`df[['Height', 'Weight']]`，因此所有表方法和属性都可以在自定义函数中相应地使用，同时只需保证自定义函数的返回为布尔值即可。\n",
    "\n",
    "例如，在原表中通过过滤得到所有容量大于100的组："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "   Height  Weight\n",
       "0   158.9    46.0\n",
       "3     NaN    41.0\n",
       "5   158.0    51.0\n",
       "6   162.5    52.0\n",
       "7   161.9    50.0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>Height</th>\n      <th>Weight</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>158.9</td>\n      <td>46.0</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>NaN</td>\n      <td>41.0</td>\n    </tr>\n    <tr>\n      <th>5</th>\n      <td>158.0</td>\n      <td>51.0</td>\n    </tr>\n    <tr>\n      <th>6</th>\n      <td>162.5</td>\n      <td>52.0</td>\n    </tr>\n    <tr>\n      <th>7</th>\n      <td>161.9</td>\n      <td>50.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 29
    }
   ],
   "source": [
    "gb.filter(lambda x: x.shape[0] > 100).head()"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "从概念上说，索引功能是组过滤功能的子集，请使用`filter`函数完成`loc[...]`的功能，这里假设\"`...`\"是元素列表。 \n",
    "#### 【END】\n",
    "## 四、跨列分组\n",
    "### 1. apply的引入\n",
    "之前几节介绍了三大分组操作，但事实上还有一种常见的分组场景，无法用前面介绍的任何一种方法处理，例如现在如下定义身体质量指数BMI：\n",
    "$${\\rm BMI} = {\\rm\\frac{Weight}{Height^2}}$$\n",
    "其中体重和身高的单位分别为千克和米，需要分组计算组BMI的均值。\n",
    "\n",
    "首先，这显然不是过滤操作，因此`filter`不符合要求；其次，返回的均值是标量而不是序列，因此`transform`不符合要求；最后，似乎使用`agg`函数能够处理，但是之前强调过聚合函数是逐列处理的，而不能够$\\color{#FF0000}{多列数据同时处理}$。由此，引出了`apply`函数来解决这一问题。\n",
    "\n",
    "### 2. apply的使用\n",
    "在设计上，`apply`的自定义函数传入参数与`filter`完全一致，只不过后者只允许返回布尔值。现如下解决上述计算问题："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Gender\n",
       "Female    18.860930\n",
       "Male      24.318654\n",
       "dtype: float64"
      ]
     },
     "metadata": {},
     "execution_count": 30
    }
   ],
   "source": [
    "def BMI(x):\n",
    "    Height = x['Height']/100\n",
    "    Weight = x['Weight']\n",
    "    BMI_value = Weight/Height**2\n",
    "    return BMI_value.mean()\n",
    "gb.apply(BMI)"
   ]
  },
  {
   "source": [
    "除了返回标量之外，`apply`方法还可以返回一维`Series`和二维`DataFrame`，但它们产生的数据框维数和多级索引的层数应当如何变化？下面举三组例子就非常容易明白结果是如何生成的："
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "【a】标量情况：结果得到的是 ``Series`` ，索引与 ``agg`` 的结果一致"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Gender  Test_Number\n",
       "Female  1              0\n",
       "        2              0\n",
       "        3              0\n",
       "Male    1              0\n",
       "        2              0\n",
       "        3              0\n",
       "dtype: int64"
      ]
     },
     "metadata": {},
     "execution_count": 31
    }
   ],
   "source": [
    "gb = df.groupby(['Gender','Test_Number'])[['Height','Weight']]\n",
    "gb.apply(lambda x: 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Gender  Test_Number\n",
       "Female  1              [0, 0]\n",
       "        2              [0, 0]\n",
       "        3              [0, 0]\n",
       "Male    1              [0, 0]\n",
       "        2              [0, 0]\n",
       "        3              [0, 0]\n",
       "dtype: object"
      ]
     },
     "metadata": {},
     "execution_count": 32
    }
   ],
   "source": [
    "gb.apply(lambda x: [0, 0]) # 虽然是列表，但是作为返回值仍然看作标量"
   ]
  },
  {
   "source": [
    "【b】`Series`情况：得到的是`DataFrame`，行索引与标量情况一致，列索引为`Series`的索引"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "                    a  b\n",
       "Gender Test_Number      \n",
       "Female 1            0  0\n",
       "       2            0  0\n",
       "       3            0  0\n",
       "Male   1            0  0\n",
       "       2            0  0\n",
       "       3            0  0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th></th>\n      <th>a</th>\n      <th>b</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th>Test_Number</th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th rowspan=\"3\" valign=\"top\">Female</th>\n      <th>1</th>\n      <td>0</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>0</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>0</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th rowspan=\"3\" valign=\"top\">Male</th>\n      <th>1</th>\n      <td>0</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>0</td>\n      <td>0</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>0</td>\n      <td>0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 33
    }
   ],
   "source": [
    "gb.apply(lambda x: pd.Series([0,0],index=['a','b']))"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "请尝试在`apply`传入的自定义函数中，根据组的某些特征返回相同长度但索引不同的`Series`，会报错吗？\n",
    "#### 【END】\n",
    "【c】`DataFrame`情况：得到的是`DataFrame`，行索引最内层在每个组原先`agg`的结果索引上，再加一层返回的`DataFrame`行索引，同时分组结果`DataFrame`的列索引和返回的`DataFrame`列索引一致。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "                        w    y\n",
       "                        x    z\n",
       "Gender Test_Number            \n",
       "Female 1           a  1.0  1.0\n",
       "                   b  1.0  1.0\n",
       "       2           a  1.0  1.0\n",
       "                   b  1.0  1.0\n",
       "       3           a  1.0  1.0\n",
       "                   b  1.0  1.0\n",
       "Male   1           a  1.0  1.0\n",
       "                   b  1.0  1.0\n",
       "       2           a  1.0  1.0\n",
       "                   b  1.0  1.0\n",
       "       3           a  1.0  1.0\n",
       "                   b  1.0  1.0"
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead tr th {\n        text-align: left;\n    }\n\n    .dataframe thead tr:last-of-type th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr>\n      <th></th>\n      <th></th>\n      <th></th>\n      <th>w</th>\n      <th>y</th>\n    </tr>\n    <tr>\n      <th></th>\n      <th></th>\n      <th></th>\n      <th>x</th>\n      <th>z</th>\n    </tr>\n    <tr>\n      <th>Gender</th>\n      <th>Test_Number</th>\n      <th></th>\n      <th></th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th rowspan=\"6\" valign=\"top\">Female</th>\n      <th rowspan=\"2\" valign=\"top\">1</th>\n      <th>a</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th>b</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th rowspan=\"2\" valign=\"top\">2</th>\n      <th>a</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th>b</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th rowspan=\"2\" valign=\"top\">3</th>\n      <th>a</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th>b</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th rowspan=\"6\" valign=\"top\">Male</th>\n      <th rowspan=\"2\" valign=\"top\">1</th>\n      <th>a</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th>b</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th rowspan=\"2\" valign=\"top\">2</th>\n      <th>a</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th>b</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th rowspan=\"2\" valign=\"top\">3</th>\n      <th>a</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n    <tr>\n      <th>b</th>\n      <td>1.0</td>\n      <td>1.0</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 34
    }
   ],
   "source": [
    "gb.apply(lambda x: pd.DataFrame(np.ones((2,2)), index = ['a','b'], columns=pd.Index([('w','x'),('y','z')])))"
   ]
  },
  {
   "source": [
    "#### 【练一练】\n",
    "请尝试在`apply`传入的自定义函数中，根据组的某些特征返回相同大小但列索引不同的`DataFrame`，会报错吗？如果只是行索引不同，会报错吗？\n",
    "#### 【END】\n",
    "最后需要强调的是，`apply`函数的灵活性是以牺牲一定性能为代价换得的，除非需要使用跨列处理的分组处理，否则应当使用其他专门设计的`groupby`对象方法，否则在性能上会存在较大的差距。同时，在使用聚合函数和变换函数时，也应当优先使用内置函数，它们经过了高度的性能优化，一般而言在速度上都会快于用自定义函数来实现。\n",
    "#### 【练一练】\n",
    "在`groupby`对象中还定义了`cov`和`corr`函数，从概念上说也属于跨列的分组处理。请利用之前定义的`gb`对象，使用apply函数实现与`gb.cov()`同样的功能并比较它们的性能。\n",
    "#### 【END】\n",
    "## 五、练习\n",
    "### Ex1：汽车数据集\n",
    "现有一份汽车数据集，其中`Brand, Disp., HP`分别代表汽车品牌、发动机蓄量、发动机输出。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "             Brand  Price Country  Reliability  Mileage   Type  Weight  Disp.  \\\n",
       "0   Eagle Summit 4   8895     USA          4.0       33  Small    2560     97   \n",
       "1  Ford Escort   4   7402     USA          2.0       33  Small    2345    114   \n",
       "2   Ford Festiva 4   6319   Korea          4.0       37  Small    1845     81   \n",
       "\n",
       "    HP  \n",
       "0  113  \n",
       "1   90  \n",
       "2   63  "
      ],
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>Brand</th>\n      <th>Price</th>\n      <th>Country</th>\n      <th>Reliability</th>\n      <th>Mileage</th>\n      <th>Type</th>\n      <th>Weight</th>\n      <th>Disp.</th>\n      <th>HP</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>Eagle Summit 4</td>\n      <td>8895</td>\n      <td>USA</td>\n      <td>4.0</td>\n      <td>33</td>\n      <td>Small</td>\n      <td>2560</td>\n      <td>97</td>\n      <td>113</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>Ford Escort   4</td>\n      <td>7402</td>\n      <td>USA</td>\n      <td>2.0</td>\n      <td>33</td>\n      <td>Small</td>\n      <td>2345</td>\n      <td>114</td>\n      <td>90</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>Ford Festiva 4</td>\n      <td>6319</td>\n      <td>Korea</td>\n      <td>4.0</td>\n      <td>37</td>\n      <td>Small</td>\n      <td>1845</td>\n      <td>81</td>\n      <td>63</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "metadata": {},
     "execution_count": 35
    }
   ],
   "source": [
    "df = pd.read_csv('../data/car.csv')\n",
    "df.head(3)"
   ]
  },
  {
   "source": [
    "1. 先过滤出所属`Country`数超过2个的汽车，即若该汽车的`Country`在总体数据集中出现次数不超过2则剔除，再按`Country`分组计算价格均值、价格变异系数、该`Country`的汽车数量，其中变异系数的计算方法是标准差除以均值，并在结果中把变异系数重命名为`CoV`。\n",
    "2. 按照表中位置的前三分之一、中间三分之一和后三分之一分组，统计`Price`的均值。\n",
    "3. 对类型`Type`分组，对`Price`和`HP`分别计算最大值和最小值，结果会产生多级索引，请用下划线把多级列索引合并为单层索引。\n",
    "4. 对类型`Type`分组，对`HP`进行组内的`min-max`归一化。\n",
    "5. 对类型`Type`分组，计算`Disp.`与`HP`的相关系数。"
   ],
   "cell_type": "markdown",
   "metadata": {}
  },
  {
   "source": [
    "### Ex2：实现transform函数\n",
    "* `groupby`对象的构造方法是`my_groupby(df, group_cols)`\n",
    "* 支持单列分组与多列分组\n",
    "* 支持带有标量广播的`my_groupby(df)[col].transform(my_func)`功能\n",
    "* `pandas`的`transform`不能跨列计算，请支持此功能，即仍返回`Series`但`col`参数为多列\n",
    "* 无需考虑性能与异常处理，只需实现上述功能，在给出测试样例的同时与`pandas`中的`transform`对比结果是否一致"
   ],
   "cell_type": "markdown",
   "metadata": {}
  }
 ]
}